package com.netflexitysolutions.amazonws.sdb.orm.filter;

import java.util.List;

import org.apache.commons.lang.StringUtils;

import com.netflexitysolutions.amazonws.sdb.orm.internal.util.StringConstants;
import com.netflexitysolutions.amazonws.sdb.orm.metadata.AttributeMetadata;
import com.netflexitysolutions.amazonws.sdb.orm.metadata.IdMetadata;
import com.netflexitysolutions.amazonws.sdb.orm.metadata.ItemMetadata;
import com.netflexitysolutions.amazonws.sdb.orm.metadata.ManyToManyMetadata;
import com.netflexitysolutions.amazonws.sdb.orm.metadata.ManyToOneMetadata;
import com.netflexitysolutions.amazonws.sdb.orm.metadata.SimpleDBMapping;

/**
 * A Predicate that represents a simple comparison condition.
 * 
 * @author netflexity
 *
 * @param <T>
 */
public class SimplePredicate<T> extends Predicate<T> {
    
    /**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	protected final Class<T> type;
    protected final String propertyName;
    protected final Object comparisonValue;
    protected final ComparisonType comparisonType;
    protected String value;
    protected String columnName;
    protected boolean caseInsensitive;
    protected SimpleDBMapping mapping;
    protected ItemMetadata itemMetadata;
    protected AttributeMetadata metadata;
    private StringBuffer query = new StringBuffer(128);

    /**
     * @param comparisonType
     * @param type
     * @param mapping
     * @param propertyName
     * @param comparisonValue
     * @param caseInsensitive
     */
    protected SimplePredicate(ComparisonType comparisonType, Class<T> type, SimpleDBMapping mapping, String propertyName, Object comparisonValue, boolean caseInsensitive) {
    	if (comparisonType.getType() == ComparisonType.EQUALS.getType() && comparisonValue == null)
            comparisonType = ComparisonType.IS_NULL;
        if (comparisonType.getType() == ComparisonType.NOT_EQUALS.getType() && comparisonValue == null)
            comparisonType = ComparisonType.NOT_NULL;
        
        this.comparisonType = comparisonType;
        this.comparisonValue = comparisonValue;
        this.propertyName = propertyName;
        this.type = type;
        this.mapping = mapping;
        this.caseInsensitive = caseInsensitive;
        
    	init();
        
        // Set Amazon SimpleDB value.
        this.value = metadata.toSimpleDBValue(comparisonValue);
        
        // Construct query.
        createQuery();
    }

    /**
     * @param comparisonType
     * @param type
     * @param mapping
     * @param propertyName
     * @param comparisonValues
     */
    protected SimplePredicate(ComparisonType comparisonType, Class<T> type, SimpleDBMapping mapping, String propertyName, Object comparisonValues[]) {
    	if (comparisonType.getType() == ComparisonType.EQUALS.getType() && comparisonValues == null)
            comparisonType = ComparisonType.IS_NULL;
        if (comparisonType.getType() == ComparisonType.NOT_EQUALS.getType() && comparisonValues == null)
            comparisonType = ComparisonType.NOT_NULL;
        
        this.comparisonType = comparisonType;
        this.comparisonValue = comparisonValues;
        this.propertyName = propertyName;
        this.type = type;
        this.mapping = mapping;
        
    	init();
    	
        int len = comparisonValues.length;
        StringBuffer values = new StringBuffer(8 * len);
        for (int i = 0; i < len; i++) {
        	String simpleDBValue = metadata.toSimpleDBValue(comparisonValues[i]);
            values.append(StringConstants.DOUBLE_QUOTE).append(simpleDBValue).append(StringConstants.DOUBLE_QUOTE);
            if (i != (len - 1)) {
                values.append(StringConstants.COMMA);
            }
        }
        this.value = values.toString();
        
        // Construct query.
        createQuery();
    }
    
    
    
    /**
     * @param comparisonType
     * @param type
     * @param mapping
     * @param propertyName
     * @param comparisonValue
     */
    protected SimplePredicate(ComparisonType comparisonType, Class<T> type, SimpleDBMapping mapping, String propertyName, Object comparisonValue) {
        this(comparisonType, type, mapping, propertyName, comparisonValue, false);
    }

    /**
     * Initialize variables.
     */
    private void init(){
        this.itemMetadata = mapping.getItemMetadataByClass(type);
        assert(itemMetadata != null);
        
        this.metadata = itemMetadata.getAttributeMetadataByName(propertyName);
        if(this.metadata == null){
        	List<ManyToOneMetadata> manyToOne = itemMetadata.getManyToOne();
        	for (ManyToOneMetadata m2o : manyToOne) {
        		if(propertyName.equals(m2o.getName())){
        			this.metadata = m2o;
        			this.columnName = metadata.getColumnName();
        		}
			}
        	
        	if(this.metadata == null){
            	List<ManyToManyMetadata> manyToMany = itemMetadata.getManyToMany();
            	for (ManyToManyMetadata m2m : manyToMany) {
            		if(propertyName.equals(m2m.getName())){
            			this.metadata = m2m;
            			this.columnName = metadata.getColumnName();
            		}
    			}
            }
        	
        	if(this.metadata == null){
            	IdMetadata id = itemMetadata.getId();
            	if(id.getName().equals(propertyName)){
            		this.metadata = id;
            	}
            }
        }
        else{
        	this.columnName = metadata.getColumnName();
        }
        assert(metadata != null);
    }
    
    /**
     * Construct query.
     */
    private void createQuery() {
        // Get condition operator.
        String condition = comparisonType.getOperator();
        
        // Construct query.
        String field = caseInsensitive ? metadata.getColumnName() : metadata.getColumnName();
        String literal = this.value;
        literal = caseInsensitive ? literal.toLowerCase() : literal;
        if(comparisonType.getType() != ComparisonType.IN.getType() && comparisonType.getType() != ComparisonType.NOT_IN.getType()){
        	query.append(StringConstants.TICK);
        }
        query.append(field);
        if(comparisonType.getType() != ComparisonType.IN.getType() && comparisonType.getType() != ComparisonType.NOT_IN.getType()){
        	query.append(StringConstants.TICK);
        }
        query.append(condition);
        if(comparisonType.getType() == ComparisonType.IN.getType() || comparisonType.getType() == ComparisonType.NOT_IN.getType()){
        	query.append(StringConstants.LP);
        }
        else{
        	query.append(StringConstants.DOUBLE_QUOTE);
        }
        query.append(literal);
        if(comparisonType.getType() == ComparisonType.IN.getType() || comparisonType.getType() == ComparisonType.NOT_IN.getType()){
        	query.append(StringConstants.RP);
        }
        else{
        	query.append(StringConstants.DOUBLE_QUOTE);
        }
    }

	/* (non-Javadoc)
	 * @see com.netflexitysolutions.amazonws.sdb.orm.filter.Filter#getType()
	 */
	public Class<T> getType() {
		return type;
	}

	/* (non-Javadoc)
	 * @see com.netflexitysolutions.amazonws.sdb.orm.filter.Filter#toSelect()
	 */
	public String toSelect() {
		return query.toString();
	}

	/* (non-Javadoc)
	 * @see com.netflexitysolutions.amazonws.sdb.orm.filter.Filter#filter(java.lang.Object)
	 */
	public boolean filter(T record) {
        if (type != null && !record.getClass().equals(type))
            return true;
        
        String valueToCompareAgainst = metadata.toSimpleDB(record);
        if(valueToCompareAgainst != null){
        	valueToCompareAgainst = caseInsensitive ? valueToCompareAgainst.toLowerCase() : valueToCompareAgainst;
        }
        
        switch (comparisonType) {
            case IS_NULL : 
                return valueToCompareAgainst == null;
            case NOT_NULL :
                return valueToCompareAgainst != null;
            case EQUALS :
                return value.equals(valueToCompareAgainst);
            case NOT_EQUALS :
                return !value.equals(valueToCompareAgainst);
            case LESS_THAN :
                return compare(valueToCompareAgainst, value) < 0;
            case GREATER_THAN :
                return compare(valueToCompareAgainst, value) > 0;
            case LESS_THAN_EQUALS :
                return compare(valueToCompareAgainst, value) <= 0;
            case GREATER_THAN_EQUALS :
                return compare(valueToCompareAgainst, value) >= 0;
            case LIKE :
            {
			    if (valueToCompareAgainst != null) {
			    	return SimpleDBSelectQueryUtil.matchLikeString(valueToCompareAgainst, value);
			    } 
			    else {
			    	return false;
			    }
			}
            case NOT_LIKE :
			{
			    if (valueToCompareAgainst != null) {
			    	return !SimpleDBSelectQueryUtil.matchLikeString(valueToCompareAgainst, value);
			    } 
			    else {
			    	return false;
			    }
			}
            case IN :
            {
			    if (valueToCompareAgainst != null) {
			    	String[] values = StringUtils.split(this.value, ',');
			    	if(values != null){
				    	for (String v : values) {
							if(valueToCompareAgainst.equals(v)){
								return true;
							}
						}
			    	}
			    	return false;
			    } 
			    else {
			    	return false;
			    }
			}
            case NOT_IN :
            {
			    if (valueToCompareAgainst != null) {
			    	String[] values = StringUtils.split(this.value, ',');
			    	if(values != null){
			    		boolean match = false;
				    	for (String v : values) {
							if(valueToCompareAgainst.equals(v)){
								match = true;
								break;
							}
						}
				    	return !match;
			    	}
			    	return false;
			    } 
			    else {
			    	return false;
			    }
			}
        }
        throw new UnsupportedOperationException();
    }

    /* (non-Javadoc)
     * @see com.netflexitysolutions.amazonws.sdb.orm.filter.Predicate#getComplement()
     */
    public Predicate<T> getComplement() {
        if (complement == null) {
            complement = new SimplePredicate<T>(ComparisonType.getEnum(-1 * comparisonType.getType()), 
                                             type,
                                             mapping,
                                             propertyName,
                                             comparisonValue);
            complement.complement = this;
        }
        return complement;
    }

    /**
     * @param o1
     * @param o2
     * @return
     */
    private int compare(String o1, String o2) {
        return ((Comparable<String>)o1).compareTo(o2);
    }
}
